// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_IC_ACCESSOR_ASSEMBLER_H_
#define V8_IC_ACCESSOR_ASSEMBLER_H_

#include "src/code-stub-assembler.h"

namespace v8 {
namespace internal {

    namespace compiler {
        class CodeAssemblerState;
    }

    class ExitPoint;

    class V8_EXPORT_PRIVATE AccessorAssembler : public CodeStubAssembler {
    public:
        using Node = compiler::Node;
        template <class T>
        using TNode = compiler::TNode<T>;
        template <class T>
        using SloppyTNode = compiler::SloppyTNode<T>;

        explicit AccessorAssembler(compiler::CodeAssemblerState* state)
            : CodeStubAssembler(state)
        {
        }

        void GenerateLoadIC();
        void GenerateLoadIC_Megamorphic();
        void GenerateLoadIC_Noninlined();
        void GenerateLoadIC_Uninitialized();
        void GenerateLoadICTrampoline();
        void GenerateLoadICTrampoline_Megamorphic();
        void GenerateKeyedLoadIC();
        void GenerateKeyedLoadIC_Megamorphic();
        void GenerateKeyedLoadIC_PolymorphicName();
        void GenerateKeyedLoadICTrampoline();
        void GenerateKeyedLoadICTrampoline_Megamorphic();
        void GenerateStoreIC();
        void GenerateStoreICTrampoline();
        void GenerateStoreGlobalIC();
        void GenerateStoreGlobalICTrampoline();
        void GenerateCloneObjectIC();
        void GenerateCloneObjectIC_Slow();
        void GenerateKeyedHasIC();
        void GenerateKeyedHasIC_Megamorphic();
        void GenerateKeyedHasIC_PolymorphicName();

        void GenerateLoadGlobalIC(TypeofMode typeof_mode);
        void GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode);

        void GenerateKeyedStoreIC();
        void GenerateKeyedStoreICTrampoline();

        void GenerateStoreInArrayLiteralIC();

        void TryProbeStubCache(StubCache* stub_cache, Node* receiver, Node* name,
            Label* if_handler, TVariable<MaybeObject>* var_handler,
            Label* if_miss);

        Node* StubCachePrimaryOffsetForTesting(Node* name, Node* map)
        {
            return StubCachePrimaryOffset(name, map);
        }
        Node* StubCacheSecondaryOffsetForTesting(Node* name, Node* map)
        {
            return StubCacheSecondaryOffset(name, map);
        }

        struct LoadICParameters {
            LoadICParameters(Node* context, Node* receiver, Node* name, Node* slot,
                Node* vector, Node* holder = nullptr)
                : context(context)
                , receiver(receiver)
                , name(name)
                , slot(slot)
                , vector(vector)
                , holder(holder ? holder : receiver)
            {
            }

            Node* context;
            Node* receiver;
            Node* name;
            Node* slot;
            Node* vector;
            Node* holder;
        };

        void LoadGlobalIC(Node* vector, Node* slot,
            const LazyNode<Context>& lazy_context,
            const LazyNode<Name>& lazy_name, TypeofMode typeof_mode,
            ExitPoint* exit_point,
            ParameterMode slot_mode = SMI_PARAMETERS);

        // Specialized LoadIC for inlined bytecode handler, hand-tuned to omit frame
        // construction on common paths.
        void LoadIC_BytecodeHandler(const LoadICParameters* p, ExitPoint* exit_point);

        // Loads dataX field from the DataHandler object.
        TNode<MaybeObject> LoadHandlerDataField(SloppyTNode<DataHandler> handler,
            int data_index);

    protected:
        struct StoreICParameters : public LoadICParameters {
            StoreICParameters(Node* context, Node* receiver, Node* name,
                SloppyTNode<Object> value, Node* slot, Node* vector)
                : LoadICParameters(context, receiver, name, slot, vector)
                , value(value)
            {
            }
            SloppyTNode<Object> value;
        };

        enum class LoadAccessMode { kLoad,
            kHas };
        enum class ICMode { kNonGlobalIC,
            kGlobalIC };
        enum ElementSupport { kOnlyProperties,
            kSupportElements };
        void HandleStoreICHandlerCase(
            const StoreICParameters* p, TNode<MaybeObject> handler, Label* miss,
            ICMode ic_mode, ElementSupport support_elements = kOnlyProperties);
        enum StoreTransitionMapFlags {
            kCheckPrototypeValidity = 1 << 0,
            kValidateTransitionHandler = 1 << 1,
            kStoreTransitionMapFlagsMask = kCheckPrototypeValidity | kValidateTransitionHandler,
        };
        void HandleStoreICTransitionMapHandlerCase(const StoreICParameters* p,
            TNode<Map> transition_map,
            Label* miss,
            StoreTransitionMapFlags flags);

        void JumpIfDataProperty(Node* details, Label* writable, Label* readonly);

        void InvalidateValidityCellIfPrototype(Node* map, Node* bitfield2 = nullptr);

        void OverwriteExistingFastDataProperty(Node* object, Node* object_map,
            Node* descriptors,
            Node* descriptor_name_index,
            Node* details, Node* value,
            Label* slow,
            bool do_transitioning_store);

        void CheckFieldType(TNode<DescriptorArray> descriptors, Node* name_index,
            Node* representation, Node* value, Label* bailout);

    private:
        // Stub generation entry points.

        // LoadIC contains the full LoadIC logic, while LoadIC_Noninlined contains
        // logic not inlined into Ignition bytecode handlers.
        void LoadIC(const LoadICParameters* p);
        void LoadIC_Noninlined(const LoadICParameters* p, Node* receiver_map,
            TNode<HeapObject> feedback,
            TVariable<MaybeObject>* var_handler, Label* if_handler,
            Label* miss, ExitPoint* exit_point);

        TNode<Object> LoadDescriptorValue(TNode<Map> map,
            TNode<IntPtrT> descriptor_entry);
        TNode<MaybeObject> LoadDescriptorValueOrFieldType(
            TNode<Map> map, TNode<IntPtrT> descriptor_entry);

        void LoadIC_Uninitialized(const LoadICParameters* p);

        void KeyedLoadIC(const LoadICParameters* p, LoadAccessMode access_mode);
        void KeyedLoadICGeneric(const LoadICParameters* p);
        void KeyedLoadICPolymorphicName(const LoadICParameters* p,
            LoadAccessMode access_mode);
        void StoreIC(const StoreICParameters* p);
        void StoreGlobalIC(const StoreICParameters* p);
        void StoreGlobalIC_PropertyCellCase(Node* property_cell, Node* value,
            ExitPoint* exit_point, Label* miss);
        void KeyedStoreIC(const StoreICParameters* p);
        void StoreInArrayLiteralIC(const StoreICParameters* p);

        // IC dispatcher behavior.

        // Checks monomorphic case. Returns {feedback} entry of the vector.
        TNode<MaybeObject> TryMonomorphicCase(Node* slot, Node* vector,
            Node* receiver_map, Label* if_handler,
            TVariable<MaybeObject>* var_handler,
            Label* if_miss);
        void HandlePolymorphicCase(Node* receiver_map, TNode<WeakFixedArray> feedback,
            Label* if_handler,
            TVariable<MaybeObject>* var_handler,
            Label* if_miss);

        // LoadIC implementation.
        void HandleLoadICHandlerCase(
            const LoadICParameters* p, TNode<Object> handler, Label* miss,
            ExitPoint* exit_point, ICMode ic_mode = ICMode::kNonGlobalIC,
            OnNonExistent on_nonexistent = OnNonExistent::kReturnUndefined,
            ElementSupport support_elements = kOnlyProperties,
            LoadAccessMode access_mode = LoadAccessMode::kLoad);

        void HandleLoadICSmiHandlerCase(const LoadICParameters* p, Node* holder,
            SloppyTNode<Smi> smi_handler,
            SloppyTNode<Object> handler, Label* miss,
            ExitPoint* exit_point,
            OnNonExistent on_nonexistent,
            ElementSupport support_elements,
            LoadAccessMode access_mode);

        void HandleLoadICProtoHandler(const LoadICParameters* p, Node* handler,
            Variable* var_holder, Variable* var_smi_handler,
            Label* if_smi_handler, Label* miss,
            ExitPoint* exit_point, ICMode ic_mode,
            LoadAccessMode access_mode);

        void HandleLoadCallbackProperty(const LoadICParameters* p,
            TNode<JSObject> holder,
            TNode<WordT> handler_word,
            ExitPoint* exit_point);

        void HandleLoadAccessor(const LoadICParameters* p,
            TNode<CallHandlerInfo> call_handler_info,
            TNode<WordT> handler_word, TNode<DataHandler> handler,
            TNode<IntPtrT> handler_kind, ExitPoint* exit_point);

        void HandleLoadField(Node* holder, Node* handler_word,
            Variable* var_double_value, Label* rebox_double,
            ExitPoint* exit_point);

        void EmitAccessCheck(Node* expected_native_context, Node* context,
            Node* receiver, Label* can_access, Label* miss);

        void HandleLoadICSmiHandlerLoadNamedCase(
            const LoadICParameters* p, Node* holder, TNode<IntPtrT> handler_kind,
            TNode<WordT> handler_word, Label* rebox_double,
            Variable* var_double_value, SloppyTNode<Object> handler, Label* miss,
            ExitPoint* exit_point, OnNonExistent on_nonexistent,
            ElementSupport support_elements);

        void HandleLoadICSmiHandlerHasNamedCase(const LoadICParameters* p,
            Node* holder,
            TNode<IntPtrT> handler_kind,
            Label* miss, ExitPoint* exit_point);

        // LoadGlobalIC implementation.

        void LoadGlobalIC_TryPropertyCellCase(
            TNode<FeedbackVector> vector, Node* slot,
            const LazyNode<Context>& lazy_context, ExitPoint* exit_point,
            Label* try_handler, Label* miss,
            ParameterMode slot_mode = SMI_PARAMETERS);

        void LoadGlobalIC_TryHandlerCase(TNode<FeedbackVector> vector, Node* slot,
            const LazyNode<Context>& lazy_context,
            const LazyNode<Name>& lazy_name,
            TypeofMode typeof_mode,
            ExitPoint* exit_point, Label* miss,
            ParameterMode slot_mode);

        // StoreIC implementation.

        void HandleStoreICProtoHandler(const StoreICParameters* p,
            TNode<StoreHandler> handler, Label* miss,
            ICMode ic_mode,
            ElementSupport support_elements);
        void HandleStoreICSmiHandlerCase(Node* handler_word, Node* holder,
            Node* value, Label* miss);
        void HandleStoreFieldAndReturn(Node* handler_word, Node* holder,
            Representation representation, Node* value,
            Label* miss);

        void CheckPrototypeValidityCell(Node* maybe_validity_cell, Label* miss);
        void HandleStoreICNativeDataProperty(const StoreICParameters* p, Node* holder,
            Node* handler_word);

        void HandleStoreToProxy(const StoreICParameters* p, Node* proxy, Label* miss,
            ElementSupport support_elements);

        void HandleStoreAccessor(const StoreICParameters* p, Node* holder,
            Node* handler_word);

        // KeyedLoadIC_Generic implementation.

        void GenericElementLoad(Node* receiver, Node* receiver_map,
            SloppyTNode<Int32T> instance_type, Node* index,
            Label* slow);

        enum UseStubCache { kUseStubCache,
            kDontUseStubCache };
        void GenericPropertyLoad(Node* receiver, Node* receiver_map,
            SloppyTNode<Int32T> instance_type,
            const LoadICParameters* p, Label* slow,
            UseStubCache use_stub_cache = kUseStubCache);

        // Low-level helpers.

        using OnCodeHandler = std::function<void(Node* code_handler)>;
        using OnFoundOnReceiver = std::function<void(Node* properties, Node* name_index)>;

        template <typename ICHandler, typename ICParameters>
        Node* HandleProtoHandler(const ICParameters* p, Node* handler,
            const OnCodeHandler& on_code_handler,
            const OnFoundOnReceiver& on_found_on_receiver,
            Label* miss, ICMode ic_mode);

        Node* PrepareValueForStore(Node* handler_word, Node* holder,
            Representation representation, Node* value,
            Label* bailout);

        void BranchIfPrototypeShouldbeFast(Node* receiver_map,
            Label* prototype_not_fast,
            Label* prototype_fast);

        // Extends properties backing store by JSObject::kFieldsAdded elements,
        // returns updated properties backing store.
        Node* ExtendPropertiesBackingStore(Node* object, Node* index);

        void StoreNamedField(Node* handler_word, Node* object, bool is_inobject,
            Representation representation, Node* value,
            Label* bailout);

        void EmitFastElementsBoundsCheck(Node* object, Node* elements,
            Node* intptr_index,
            Node* is_jsarray_condition, Label* miss);
        void EmitElementLoad(Node* object, Node* elements, Node* elements_kind,
            SloppyTNode<IntPtrT> key, Node* is_jsarray_condition,
            Label* if_hole, Label* rebox_double,
            Variable* var_double_value,
            Label* unimplemented_elements_kind, Label* out_of_bounds,
            Label* miss, ExitPoint* exit_point,
            LoadAccessMode access_mode = LoadAccessMode::kLoad);
        void NameDictionaryNegativeLookup(Node* object, SloppyTNode<Name> name,
            Label* miss);
        TNode<BoolT> IsPropertyDetailsConst(Node* details);

        // Stub cache access helpers.

        // This enum is used here as a replacement for StubCache::Table to avoid
        // including stub cache header.
        enum StubCacheTable : int;

        Node* StubCachePrimaryOffset(Node* name, Node* map);
        Node* StubCacheSecondaryOffset(Node* name, Node* seed);

        void TryProbeStubCacheTable(StubCache* stub_cache, StubCacheTable table_id,
            Node* entry_offset, Node* name, Node* map,
            Label* if_handler,
            TVariable<MaybeObject>* var_handler,
            Label* if_miss);
    };

    // Abstraction over direct and indirect exit points. Direct exits correspond to
    // tailcalls and Return, while indirect exits store the result in a variable
    // and then jump to an exit label.
    class ExitPoint {
    private:
        using Node = compiler::Node;
        using CodeAssemblerLabel = compiler::CodeAssemblerLabel;
        using CodeAssemblerVariable = compiler::CodeAssemblerVariable;

    public:
        using IndirectReturnHandler = std::function<void(Node* result)>;

        explicit ExitPoint(CodeStubAssembler* assembler)
            : ExitPoint(assembler, nullptr)
        {
        }

        ExitPoint(CodeStubAssembler* assembler,
            const IndirectReturnHandler& indirect_return_handler)
            : asm_(assembler)
            , indirect_return_handler_(indirect_return_handler)
        {
        }

        ExitPoint(CodeStubAssembler* assembler, CodeAssemblerLabel* out,
            CodeAssemblerVariable* var_result)
            : ExitPoint(assembler, [=](Node* result) {
                var_result->Bind(result);
                assembler->Goto(out);
            })
        {
            DCHECK_EQ(out != nullptr, var_result != nullptr);
        }

        template <class... TArgs>
        void ReturnCallRuntime(Runtime::FunctionId function, Node* context,
            TArgs... args)
        {
            if (IsDirect()) {
                asm_->TailCallRuntime(function, context, args...);
            } else {
                indirect_return_handler_(asm_->CallRuntime(function, context, args...));
            }
        }

        template <class... TArgs>
        void ReturnCallStub(Callable const& callable, Node* context, TArgs... args)
        {
            if (IsDirect()) {
                asm_->TailCallStub(callable, context, args...);
            } else {
                indirect_return_handler_(asm_->CallStub(callable, context, args...));
            }
        }

        template <class... TArgs>
        void ReturnCallStub(const CallInterfaceDescriptor& descriptor, Node* target,
            Node* context, TArgs... args)
        {
            if (IsDirect()) {
                asm_->TailCallStub(descriptor, target, context, args...);
            } else {
                indirect_return_handler_(
                    asm_->CallStub(descriptor, target, context, args...));
            }
        }

        void Return(Node* const result)
        {
            if (IsDirect()) {
                asm_->Return(result);
            } else {
                indirect_return_handler_(result);
            }
        }

        bool IsDirect() const { return !indirect_return_handler_; }

    private:
        CodeStubAssembler* const asm_;
        IndirectReturnHandler indirect_return_handler_;
    };

} // namespace internal
} // namespace v8

#endif // V8_IC_ACCESSOR_ASSEMBLER_H_
